﻿using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using NeuralLib.ActivationFunction;

namespace NeuralLib
{
    public class LayerStructure
    {
        public IActivationFunction Function;
        public int NeuronsCount;

        public LayerStructure(int neuronsCount, IActivationFunction f)
        {
            NeuronsCount = neuronsCount;
            Function = f;
        }
    }

    [Serializable]
    public class Network
    {
        protected int _inputsCount;

        protected Layer[] _layers;

        protected double[] _output;

        private readonly int _layersCount;

        public Network(IActivationFunction function, int inputsCount, params int[] neuronsCount)
        {
            _inputsCount = inputsCount;
            _layersCount = neuronsCount.Count();
            _layers = new Layer[_layersCount];
            _output = new double[neuronsCount.Last()];

            for (var i = 0; i < _layersCount; i++)
            {
                _layers[i] = new Layer(
                    neuronsCount[i],
                    (i == 0) ? inputsCount : neuronsCount[i - 1],
                    function);
            }
        }

        public Network(int inputsCount, params LayerStructure[] layers)
        {
            _inputsCount = inputsCount;
            _layersCount = layers.Count();
            _layers = new Layer[_layersCount];
            _output = new double[layers.Last().NeuronsCount];

            for (var i = 0; i < _layersCount; i++)
            {
                _layers[i] = new Layer(
                    layers[i].NeuronsCount,
                    (i == 0) ? inputsCount : layers[i - 1].NeuronsCount,
                    layers[i].Function);
            }
        }

        public int InputsCount
        {
            get
            {
                return _inputsCount;
            }
        }

        public Layer[] Layers
        {
            get
            {
                return _layers;
            }
        }

        public int LayersCount
        {
            get
            {
                return _layersCount;
            }
        }

        public double[] Output
        {
            get
            {
                return _output;
            }
        }

        //private IActivationFunction _activationFunction;
        //public void SetActivationFunction(IActivationFunction function)
        //{
        //    _activationFunction = function;
        //}

        public static Network Load(string fileName)
        {
            var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            var network = Load(stream);
            stream.Close();

            return network;
        }

        public static Network Load(Stream stream)
        {
            IFormatter formatter = new BinaryFormatter();
            var network = (Network)formatter.Deserialize(stream);
            return network;
        }

        public double[] Compute(double[] input)
        {
            if (_layers == null)
                throw new ArgumentNullException("Layers", "Layers can't be null");

            var output = _layers.Aggregate(input, (current, layer) => layer.Compute(current));

            _output = output;

            return output;
        }

        public void Save(string fileName)
        {
            var stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
            Save(stream);
            stream.Close();
        }

        public void Save(Stream stream)
        {
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, this);
        }
    }
}